/* @flow */

import {
  warn,
  remove,
  isObject,
  parsePath,
  _Set as Set,
  handleError,
  noop
} from '../util/index'

import { traverse } from './traverse'
import { queueWatcher } from './scheduler'
import Dep, { pushTarget, popTarget } from './dep'

import type { SimpleSet } from '../util/index'

let uid = 0

/**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  /**
   * 构建Watcher对象时传递的参数
   * @param {组件实例对象} vm 
   * @param {要观察的表达式} expOrFn 
   * @param {当被观察的表达式的值变化时的回调函数} cb 
   * @param {传递给当前观察者对象的选项} options 
   * @param {标识该观察者实例是否是渲染函数的观察者} isRenderWatcher 
   */
  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    // 将当前组件实例对象 vm 赋值给该观察者实例的 this.vm 属性（每一个观察者实例都有一个vm实例属性，指明这个观察者是属于哪一个组件的）
    this.vm = vm
    // 是否是渲染函数的观察者(仅在mountComponent 函数中创建渲染函数观察者时才为真)
    if (isRenderWatcher) {
      // 将当前观察者实例赋值给 vm._watcher 属性(组件实例的 _watcher 属性的值引用着该组件的渲染函数观察者)
      // _watcher 在initLifecycle 函数中初始化，初始值为null,
      vm._watcher = this
    }
    // 属于该组件实例的观察者都会被添加到该组件实例对象的 vm._watchers 数组,包括渲染函数的观察者和非渲染函数的观察者
    // _watchers 在initState 函数中初始化，初始值为[]
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep // 用来告诉当前观察者实例对象是否是深度观测
      this.user = !!options.user // 用来标识当前观察者实例对象是 开发者定义的 还是 内部定义的
      this.lazy = !!options.lazy // 用来标识当前观察者实例对象是否是计算属性的观察者
      this.sync = !!options.sync // 用来告诉观察者当数据变化时是否同步求值并执行回调(默认不会同步求值)
      this.before = options.before // Watcher 实例的钩子，当数据变化之后，触发更新之前，调用在创建渲染函数的观察者实例对象时传递的 before 选项
    } else {
      // 当前观察者实例对象的四个属性 this.deep、this.user、this.computed 以及 this.sync 全部初始化为 false
      this.deep = this.user = this.lazy = this.sync = false
    }
    // 定义一些实例属性
    this.cb = cb
    this.id = ++uid // 观察者实例对象的唯一标识
    this.active = true //标识该观察者实例对象是否是激活状态
    this.dirty = this.lazy // 只有计算属性的观察者实例对象的 this.dirty 属性的值才会为真
    // 下面4个属性用于避免收集重复依赖和移除无用依赖
    // newDepIds 和 newDeps 这两个属性的值所存储的总是当次求值所收集到的 Dep 实例对象
    // depIds 和 deps 这两个属性的值所存储的总是上一次求值过程中所收集到的 Dep 实例对象
    this.deps = []
    this.depIds = new Set()
    this.newDeps = []
    this.newDepIds = new Set()
    // 非生产环境下才使用，生产环境下为空字符串
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // 检测 expOrFn 的类型，如果是函数，直接为用getter的值 
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      // 否则将parsePath的返回值(返回一个新的函数)作为getter的值
      this.getter = parsePath(expOrFn)
      // 如果parsePath解析失败，getter值会是undefined，此时，会重新为getter赋一个空函数(getter一定是一个函数)，再给出警告信息
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * 观察者对象的实例方法，其作用为求值(求值的目的有两个，第一个是能够触发访问器属性的 get 拦截器函数，第二个是能够获得被观察目标的值)
   */
  get () {
    // pushTarget 函数来自于 src/core/observer/dep.js
    // 为Dep.target静态属性赋值，调用完这一步，Dep.target就存在了，所以在取值触发拦截时Dep.target是有值的
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  /**
   * 观察者实例对象的 addDep 方法.参数是一个 Dep 对象
   */
  addDep (dep: Dep) {
    const id = dep.id
    // 作用就是：避免收集重复依赖 （根据 dep.id 属性检测该 Dep 实例对象是否已经存在于 newDepIds）
    // newDepIds 属性用来避免在 一次求值 的过程中收集重复的依赖
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      // 作用就是：避免收集重复依赖(depIds 属性是用来在 多次求值 中避免收集重复依赖的) 
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

  /**
   * 每次求值完毕后都会使用 depIds 属性和 deps 属性保存 newDepIds 属性和 newDeps 属性的值
   * 然后再清空 newDepIds 属性和 newDeps 属性的值
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      // newDepIds 属性和 newDeps 属性被清空,在被清空之前把值分别赋给了 depIds 属性和 deps 属性,这两个属性将会用在下一次求值时避免依赖的重复收集
      const dep = this.deps[i]
      // 移除废弃的观察者(上一次求值所收集到的 Dep 实例对象不在当前这次求值所收集到的 Dep 实例对象中时，说明该 Dep 实例对象已经和该观察者不存在依赖关系了)
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update () {
    // 当前观察者实例对象是否是计算属性的观察者
    if (this.lazy) {
      this.dirty = true
    // 当变化发生时是否同步更新变化(默认是异步更新)
    } else if (this.sync) {
      this.run()
    } else {
      // 果没有指定这个观察者是同步更新,则将当前观察者对象放到一个异步更新队列
      queueWatcher(this);
    }
  }

  /**
   * 真正实现更新变化的操作.
   */
  run () {
    // 当前观察者是否处于激活状态
    if (this.active) {
      // 重新求值：意味着重新执行渲染函数，重新生成虚拟DOM并更新真实DOM
      const value = this.get()
      if (
        // 对比新值 value 和旧值 this.value 是否相等
        value !== this.value ||
        //判断新值的类型是否是对象(2个不同的对象可能具有相同的引用)
        isObject(value) ||
        this.deep
      ) {
        // 保存旧值并使用新值更新 this.value 的值
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            // 执行回调函数
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   * 收集依赖信息。
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    } 
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}
